/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Nvigorate.Common.Reflection;
using Nvigorate.Data.Query;
using Nvigorate.Extensions;
using Nvigorate.Relational.Builders;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;

namespace Nvigorate.Relational.Visitors
{
    public enum Operation
    {
        Persist,
        Delete
    }

    public class ManyToManyRelationshipVisitor : IPersistableVisitor
    {
        protected IRepositoryMapIndex _repositoryMapIndex;
        protected QueryList _manyToManyQueries = new QueryList();
        protected IPersistenceKeyManager _keyManager;
        protected List<object> _visited = new List<object>();
        protected Operation _operation;

        public IQueryObject GetQuery()
        {
            return _manyToManyQueries;
        }

        public TraversalDirection Direction
        {
            get { return TraversalDirection.DepthFirst; }
        }

        public void Visit(IPersistable persistable, IPersistable parent)
        {
            if(Visited(persistable))
                return;

            var map = _repositoryMapIndex.GetMap(persistable.InstanceType);
            if(_operation == Operation.Persist)
            {
                BuildPersistQueries(persistable, map);
            }
            else
            {
                BuildDeleteQueries(persistable, map);
            }

            _visited.Add(persistable.Instance);
        }

        protected void BuildPersistQueries(IPersistable persistable, IBindingMap map)
        {
            if (map.Relationships != null && map.Relationships.OfType<ManyToManyRelationship>().Count() > 0)
            {
                map.Relationships.OfType<ManyToManyRelationship>().ForEach(r =>
                    {
                        var relativeField = r.RelativeProperty ?? QueryBuilderHelper.GetDefaultChildPropertyName(persistable.InstanceType, r.RelativeType);
                        var changes = (IEnumerable)Reflector.Read(persistable.Instance, relativeField);
                        var builder = new ManyToManyRelationshipBuilder(changes, _keyManager, persistable,
                                                                        _repositoryMapIndex, r);
                        _manyToManyQueries.AddRange(builder.GetQuery());
                    });
            }
        }

        protected void BuildDeleteQueries(IPersistable persistable, IBindingMap map)
        {
            if (map.Relationships != null && map.Relationships.OfType<ManyToManyRelationship>().Count() > 0)
            {
                map.Relationships.OfType<ManyToManyRelationship>().ForEach(r => _manyToManyQueries.Add(BuildDeleteQuery(persistable, r)));
            }
        }

        protected IQueryObject BuildDeleteQuery(IPersistable persistable, ManyToManyRelationship relationship)
        {
            return new Delete(
                Table.From(relationship.Container.ContainerName), 
                Where.Criteria( new CriteriaClause(
                                    relationship.Container.SubjectPairs.Select(p => 
                                                                               Criterion.Column(p.Source, relationship.Container.ContainerName)
                                                                                   .EqualTo(new Literal(Reflector.Read(persistable.Instance, p.Target)))
                                                                                   .And())
                                    )
                    )
                );
        }

        public bool Visited(IPersistable persistable)
        {
            return _visited.Exists(i => ReferenceEquals(i, persistable.Instance));
        }

        public ManyToManyRelationshipVisitor(IPersistenceKeyManager keyManager, IRepositoryMapIndex repositoryMapIndex, Operation operation)
        {
            _keyManager = keyManager;
            _repositoryMapIndex = repositoryMapIndex;
            _operation = operation;
        }
    }
}